# Copyright (c) WanSheng Intelligent Corp. All rights reserved.


import time
from threading import Thread
import base64
from redis.client import Redis
from .handler_base import CacheDbHandlersBase
from ..model.resource_data_base import ResourceDataBase
from ..model.property_data import PropertyData
from ..model.property_data import ValueType
from ..model.property_data import parse_value

from wa_edge_iot.internals.rd_parser import RDParser

class CacheDbUtility(Thread):
    '''
    The utility tool for accessing the cacheDB redis
    '''
    def __init__(self, handler, port=6379):
        '''
        Constructor
        '''
        Thread.__init__(self)
        self.__handler = None
        self.__redis = Redis('localhost', port)
        self.__pubsub = None
        self.__quit = False
        if (handler):
            self.set_handler(handler)
        
        # always listen event of device registartion and wagent status change
    
    def set_handler(self, handler):
        assert handler is None or isinstance(handler, CacheDbHandlersBase)
        self.__handler = handler
        
        if self.__handler and self.__pubsub is None:
            self.__pubsub = self.__redis.pubsub()
            self.__pubsub.psubscribe('ch:reg:*')
            self.__pubsub.psubscribe('ch:iagent:*')
            self.__pubsub.psubscribe('ch:v:*')
            self.__pubsub.psubscribe('ch:ams:*')
            
            self.start()
        
    def get_redis(self):
        return self.__redis
    
    def quit(self):
        self.__quit = False
        if self.__pubsub:
            self.__pubsub.close()    
        
    # return type: ResourceDataBase
    '''
        [{
        "n": "cccq/0",
        "tm": 1681659878,
        "v": 0
        }, {
            "n": "cccq/1",
            "tm": 1681659878,
            "v": true
        }, {
            "n": "cccb",
            "tm": 1681659878,
            "v": "abcd"
        }]
    '''
    def get_resource_data(self, di, ri):
        # refer to app-sdk/c-api/wa_redis_api.h for the key schema
        key = "v:p:" + di + ':' + ri 
        encoded = self.__redis.hgetall(key)
        if not encoded:
            return None
        
        from .wagent import I_wagent
        parser = I_wagent().data_util().get_parser(fmt)
        if parser is None:
            data = ResourceDataBase(data, fmt)
        else:
            data = parser.parse(data, fmt)
        
        return data
    
    
    def get_property_value(self, di, ri, property_name):
            
        key = "v:p:" + di + ':' + ri
        value = self.__redis.hget(key, property_name)
        if not value:
            return None
        
        items = value.decode().split(';')
        v = None
        t = None
        type = ValueType.UNKNOWN
        if (len(items) < 2):
            v = float(value)
            type = ValueType.FLOAT
        else:
            for item in items:
                if item.startswith('v='):
                    v = item[2:]
                elif item.startswith('tm='):
                    t = int(item[3:])
                elif item.startswith('t=') and len(item[2:]) > 0:
                    ch = item[2:3]
                    if ch == 'f':
                        type = ValueType.FLOAT
                    elif ch == 'i':
                        type = ValueType.INT
                    elif ch == 's':
                        type = ValueType.STRING
                    elif ch == 'b':
                        type = ValueType.BOOL
            if type != ValueType.UNKNOWN:
                v = parse_value(v, type)
        
        return PropertyData(property_name, v, type, t)
    

    # return: RdDevice
    def get_device(self, di):
        rd = self.__redis.hget( 'device:' + di, 'rd')
        if not rd:
            return None
        try:
            data = base64.b64decode(rd)
            if not data:
                return None
        except Exception as e:
            print ('decode rd failed: ' + str(e))    
            return None
        
        devices = RDParser.parse_device(data.decode())
        if devices:
            return devices[0]
        
        return None
    
    ''' Not needed since we always listen any data change events
    def sub_data_event(self, di, ri, property = None):
        if di is None:
            self.__pubsub.subscribe('ch:v:*')
        elif ri is None:
            self.__pubsub.subscribe('ch:v:' + di + ':*')
        elif property is None:
            self.__pubsub.subscribe('ch:v:' + di + ':' + ri)
            self.__pubsub.subscribe('ch:v:' + di + ':' + ri + ':*')
        else:
            self.__pubsub.subscribe('ch:v:' + di + ':' + ri + ':' + property)
    '''
    
    def __handle_message(self, message):
        
        if message ['type'] in ['subscribe', 'psubscribe']:
            print('redis sub confirmed: '  + str(message['channel']))
            return 
        
        if self.__handler is None:
            return
        
        items = message['channel'].decode().split(':')

        if len(items) < 3:
            return
        
        if items[0] != 'ch':
            return
        
        # "ch:ams:cfg:{software name}:{target type}:{target id}"
        if items[1] == 'ams' and items[2] == 'cfg':
            config_path = message['data']
            self.__handler.on_ams_cfg_event(items[3], items[4], items[5], config_path)
            return

        if items[1] == 'iagent' and items[2] == 'ilink':
            self.__handler.on_system_event(CacheDbHandlersBase.Sys_Event_Cloud_Connection, message['data'])
            return

        if items[1] == 'reg':
            # format: {online|offline}
            if type(message['data']) not in [str, str]:
                return
            status = message['data']

            self.__handler.on_any_device_registration(items[2],  status)  
            return      
                
        if items[1] == 'v':
            if len(items) < 4:
                return

            di = items[2]        
            ri = items[3]
            property = None
            if len(items) == 5:
                property = items[4]
            self.__handler.on_cached_data_updated(di, ri, property, message['data'].decode())        
                
        return
    
    def run(self):
        for message in self.__pubsub.listen():
            self.__handle_message(message)
            if self.__quit:
                break
        
        self.__pubsub = None
        self.__redis = None

        